
/*
    Software License

    Copyright (C) 2021-05-24  Xoronos

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, version 3.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

/*
    Liabilities

    The software is provided "AS IS" without any warranty of any kind, either expressed,
    implied, or statutory, including, but not limited to, any warranty that the software
    will conform to specifications, any implied warranties of merchantability, fitness
    for a particular purpose, and freedom from infringement, and any warranty that the
    documentation will conform to the software, or any warranty that the software will
    be error free.

    In no event shall Xoronos be liable for any damages, including, but not limited to,
    direct, indirect, special or consequential damages, arising out of, resulting from,
    or in any way connected with this software, whether or not based upon warranty,
    contract, tort, or otherwise, whether or not injury was sustained by persons or
    property or otherwise, and whether or not loss was sustained from, or arose out of
    the results of, or use of, the software or services provided hereunder.

    To request the provided software under a different license you can contact us at
    support@xoronos.com
*/

#include "xrn_common.h"
#include "xrn_common_backend.h"

int xrn_dump_checksums_bytes_wrapper( uint8_t * i_bytes, uint64_t i_length, uint8_t * o_bytes,
                                      uint64_t o_length, uint64_t * o_written_bytes
 )
{
    XPRINT_DEB(  );

    uint64_t read_bytes;
    uint64_t written_bytes;

    cksumnum_t xorchsumnum;
    cksumnum_t chsumnum_init_buff[XRN_CHKSUM_BUFF];
    cksumnum_t chsumnum_buff[XRN_CHKSUM_BUFF];

    uint8_t padding_started;
    uint8_t done_read;
    uint8_t buff_index;
    uint8_t extra_read_needed;
    uint64_t i, j;

    if ( ( i_bytes == NULL ) || ( o_bytes == NULL ) || ( o_written_bytes == NULL ) ) {
        XPRINT_ERR( 2246, XERR_NULL_MSG );
        return XERR_NULL;
    }

    // output size check

    if ( o_length < ( ( ( i_length ) / sizeof( cksumnum_t ) ) + sizeof( cksumnum_t ) ) ) {
        XPRINT_ERR( 748, XERR_IO_MSG );
        return XERR_IO;
    }

    for ( i = 0; i < 32; i++ ) {
        xorchsumnum.num[i] = 0;
    }

    read_bytes = 0;
    written_bytes = 0;
    done_read = 0;
    extra_read_needed = 0;
    buff_index = 0;
    padding_started = 0;

    // filling the initial buffer

    for ( i = 0; i < XRN_CHKSUM_BUFF; i++ ) {
        for ( j = 0; j < 32; j++ ) {

            if ( read_bytes < i_length ) {

                chsumnum_init_buff[i].num[j] = i_bytes[read_bytes];
                read_bytes++;

            } else {

                done_read = 1;

                if ( padding_started == 0 ) {
                    chsumnum_init_buff[i].num[j] = 0x80;
                    padding_started = 1;
                } else {
                    chsumnum_init_buff[i].num[j] = 0x00;
                }

            }

        }

    }

    if ( ( read_bytes == i_length ) && ( done_read == 0 ) ) {
        extra_read_needed = 1;
    }

    // copy initial buffer and compute the initial xor checksum

    for ( i = 0; i < XRN_CHKSUM_BUFF; i++ ) {

        chsumnum_buff[i] = chsumnum_init_buff[i];

        for ( j = 0; j < 32; j++ ) {
            xorchsumnum.num[j] = xorchsumnum.num[j] ^ chsumnum_buff[i].num[j];
        }

    }

    // compute one output 8 bit crc

    if ( written_bytes < o_length ) {
        o_bytes[written_bytes] = cksumnum_buff_crc8( buff_index, chsumnum_buff );
        written_bytes++;
    } else {
        XPRINT_ERR( 744, XERR_IO_MSG );
        return XERR_IO;
    }

    buff_index = ( uint8_t ) ( ( buff_index + 1 ) % XRN_CHKSUM_BUFF );

    while ( done_read != 1 ) {

        // read one chsumnum

        for ( j = 0; j < 32; j++ ) {

            // append padding

            if ( extra_read_needed == 1 ) {

                if ( j == 0 ) {

                    chsumnum_buff[buff_index].num[j] = 0x80;

                } else {

                    chsumnum_buff[buff_index].num[j] = 0x00;

                }

                done_read = 1;

                // full read

            } else if ( read_bytes < i_length ) {

                chsumnum_buff[buff_index].num[j] = i_bytes[read_bytes];
                read_bytes++;

                // read plus padding

            } else {

                if ( padding_started == 0 ) {

                    chsumnum_buff[buff_index].num[j] = 0x80;
                    padding_started = 1;

                } else {

                    chsumnum_buff[buff_index].num[j] = 0x00;

                }

                done_read = 1;

            }

        }

        if ( ( read_bytes == i_length ) && ( done_read == 0 ) ) {
            extra_read_needed = 1;
        }

        // compute 256 bit xor checksum

        for ( j = 0; j < 32; j++ ) {
            xorchsumnum.num[j] = xorchsumnum.num[j] ^ chsumnum_buff[buff_index].num[j];
        }

        // compute one output 8 bit crc

        if ( written_bytes < o_length ) {
            o_bytes[written_bytes] = cksumnum_buff_crc8( buff_index, chsumnum_buff );
            written_bytes++;
        } else {
            XPRINT_ERR( 746, XERR_IO_MSG );
            return XERR_IO;
        }

        buff_index = ( uint8_t ) ( ( buff_index + 1 ) % XRN_CHKSUM_BUFF );

    }

    // final copy the initial buffer into the buffer

    for ( i = 0; i < XRN_CHKSUM_BUFF - 1; i++ ) {

        for ( j = 0; j < 32; j++ ) {
            chsumnum_buff[buff_index].num[j] = chsumnum_init_buff[i].num[j];
        }

        // compute one output 8 bit crc

        if ( written_bytes < o_length ) {
            o_bytes[written_bytes] = cksumnum_buff_crc8( buff_index, chsumnum_buff );
            written_bytes++;

        } else {
            XPRINT_ERR( 740, XERR_IO_MSG );
            return XERR_IO;
        }

        buff_index = ( uint8_t ) ( ( buff_index + 1 ) % XRN_CHKSUM_BUFF );

    }

    // copy final xor checksum

    for ( i = 0; i < 32; i++ ) {

        if ( written_bytes < o_length ) {
            o_bytes[written_bytes] = xorchsumnum.num[i];
            written_bytes++;
        } else {

            XPRINT_ERR( 751, XERR_IO_MSG );
            return XERR_IO;
        }

    }

    *o_written_bytes = written_bytes;

    return XSUCCESS;

}

int xrn_check_checksums_bytes_wrapper( uint8_t * i_bytes, uint64_t i_length, uint8_t * ck_bytes, uint64_t ck_length
 )
{
    XPRINT_DEB(  );

    uint64_t read_bytes;
    uint64_t written_bytes;
    cksumnum_t xorchsumnum;
    cksumnum_t chsumnum_init_buff[XRN_CHKSUM_BUFF];
    cksumnum_t chsumnum_buff[XRN_CHKSUM_BUFF];
    uint8_t done_read;
    uint8_t padding_started;
    uint8_t xcrc8;
    uint8_t buff_index;
    uint8_t extra_read_needed;
    uint64_t i, j;

    if ( ( i_bytes == NULL ) || ( ck_bytes == NULL ) ) {
        XPRINT_ERR( 749, XERR_NULL_MSG );
        return XERR_NULL;
    }

    // output size check

    if ( ck_length < ( ( ( i_length ) / sizeof( cksumnum_t ) ) + sizeof( cksumnum_t ) ) ) {
        XPRINT_ERR( 2247, XERR_IO_MSG );
        return XERR_IO;
    }

    for ( i = 0; i < XRN_CHKSUM_BUFF; i++ ) {
        xorchsumnum.num[i] = 0;
    }

    read_bytes = 0;
    written_bytes = 0;
    done_read = 0;
    extra_read_needed = 0;
    buff_index = 0;
    padding_started = 0;

    // filling the initial buffer

    for ( j = 0; j < sizeof( chsumnum_init_buff ); j++ ) {

        if ( read_bytes < i_length ) {

            ( ( uint8_t * ) chsumnum_init_buff )[j] = i_bytes[read_bytes];
            read_bytes++;

        } else {

            done_read = 1;

            if ( padding_started == 0 ) {
                ( ( uint8_t * ) chsumnum_init_buff )[j] = 0x80;
                padding_started = 1;
            } else {
                ( ( uint8_t * ) chsumnum_init_buff )[j] = 0x00;
            }

        }

    }

    if ( ( read_bytes == i_length ) && ( done_read == 0 ) ) {
        extra_read_needed = 1;
    }

    // copy initial buffer and compute the initial xor checksum

    for ( i = 0; i < XRN_CHKSUM_BUFF; i++ ) {

        chsumnum_buff[i] = chsumnum_init_buff[i];

        for ( j = 0; j < XRN_CHKSUM_BUFF; j++ ) {
            xorchsumnum.num[j] = xorchsumnum.num[j] ^ chsumnum_buff[i].num[j];
        }

    }

    // compute one output 8 bit crc

    if ( written_bytes < ck_length ) {

        xcrc8 = cksumnum_buff_crc8( buff_index, chsumnum_buff );

        if ( xcrc8 != ck_bytes[written_bytes] ) {
            XPRINT_NOT( 1 );
            return XERR_CRC;
        }

        written_bytes++;

    } else {
        XPRINT_ERR( 747, XERR_IO_MSG );
        return XERR_IO;
    }

    buff_index = ( uint8_t ) ( ( buff_index + 1 ) % XRN_CHKSUM_BUFF );

    while ( done_read != 1 ) {

        // read one chsumnum

        for ( j = 0; j < sizeof( cksumnum_t ); j++ ) {

            // append padding

            if ( extra_read_needed == 1 ) {

                if ( j == 0 ) {

                    chsumnum_buff[buff_index].num[j] = 0x80;

                } else {

                    chsumnum_buff[buff_index].num[j] = 0x00;

                }

                done_read = 1;

                // full read

            } else if ( read_bytes < i_length ) {

                ( ( uint8_t * ) & chsumnum_buff[buff_index] )[j] = i_bytes[read_bytes];
                read_bytes++;

                // read plus padding

            } else {

                if ( padding_started == 0 ) {

                    chsumnum_buff[buff_index].num[j] = 0x80;
                    padding_started = 1;

                } else {

                    chsumnum_buff[buff_index].num[j] = 0x00;

                }

                done_read = 1;

            }

        }

        if ( ( read_bytes == i_length ) && ( done_read == 0 ) ) {
            extra_read_needed = 1;
        }

        // compute 256 bit xor checksum

        for ( j = 0; j < XRN_CHKSUM_BUFF; j++ ) {
            xorchsumnum.num[j] = xorchsumnum.num[j] ^ chsumnum_buff[buff_index].num[j];
        }

        // compute one output 8 bit crc

        if ( written_bytes < ck_length ) {

            xcrc8 = cksumnum_buff_crc8( buff_index, chsumnum_buff );

            if ( xcrc8 != ck_bytes[written_bytes] ) {
                XPRINT_NOT( 1 );
                return XERR_CRC;
            }

            written_bytes++;

        } else {
            XPRINT_ERR( 2248, XERR_IO_MSG );
            return XERR_IO;
        }

        buff_index = ( uint8_t ) ( ( buff_index + 1 ) % XRN_CHKSUM_BUFF );

    }

    // copy final the initial buffer into the buffer

    for ( i = 0; i < XRN_CHKSUM_BUFF - 1; i++ ) {

        chsumnum_buff[buff_index] = chsumnum_init_buff[i];

        // compute one output 8 bit crc

        if ( written_bytes < ck_length ) {

            xcrc8 = cksumnum_buff_crc8( buff_index, chsumnum_buff );

            if ( xcrc8 != ck_bytes[written_bytes] ) {
                XPRINT_NOT( 1 );
                return XERR_CRC;
            }

            written_bytes++;

        } else {
            XPRINT_ERR( 745, XERR_IO_MSG );
            return XERR_IO;
        }

        buff_index = ( uint8_t ) ( ( buff_index + 1 ) % XRN_CHKSUM_BUFF );

    }

    // copy final xor checksum
    for ( i = 0; i < sizeof( xorchsumnum ); i++ ) {

        if ( written_bytes < ck_length ) {

            xcrc8 = xorchsumnum.num[i];

            if ( xcrc8 != ck_bytes[written_bytes] ) {
                XPRINT_NOT( 1 );
                return XERR_CRC;
            }

            written_bytes++;

        } else {
            XPRINT_ERR( 742, XERR_IO_MSG );
            return XERR_IO;
        }

    }

    XPRINT_NOT( 0 );
    return XSUCCESS;

}

int xrn_estimate_checksum_bytes_length( uint64_t i_length, uint64_t * o_length
 )
{
    XPRINT_DEB(  );
    uint64_t padding_size;
    uint64_t buff_size;
    uint64_t crc8_num;

    if ( o_length == NULL ) {
        XPRINT_ERR( 2235, XERR_NULL_MSG );
        return XERR_NULL;
    }

    if ( i_length < sizeof( cksumnum_t ) * XRN_CHKSUM_BUFF ) {

        buff_size = ( sizeof( cksumnum_t ) * XRN_CHKSUM_BUFF );
        crc8_num = buff_size / sizeof( cksumnum_t );
        padding_size = 0;

    } else if ( ( i_length >= sizeof( cksumnum_t ) * XRN_CHKSUM_BUFF ) &&
                ( i_length % sizeof( cksumnum_t ) == 0 ) ) {

        crc8_num = i_length / sizeof( cksumnum_t );
        padding_size = 1;

    } else {

        crc8_num = i_length / sizeof( cksumnum_t );
        padding_size = 0;

    }

    *o_length = ( uint64_t ) ( crc8_num + sizeof( cksumnum_t ) + padding_size + 1 );

    return XSUCCESS;

}

int xrn_estimate_block_bytes_length( uint64_t i_length, uint64_t * o_length
 )
{
    XPRINT_DEB(  );

    if ( o_length == NULL ) {
        XPRINT_ERR( 142, XERR_NULL_MSG );
        return XERR_NULL;
    }

    *o_length = i_length + sizeof( header_block_t ) + sizeof( uint32_t );

    return XSUCCESS;
}

int xrn_estimate_block_bytes_length_merge( uint64_t lengths[XRN_MAX_MERGE_PARALLELISM], uint8_t num_inputs,
                                           uint64_t * o_length
 )
{
    XPRINT_DEB(  );
    int i;
    uint64_t lengths_tmp;

    if ( ( o_length == NULL ) || ( lengths == NULL ) ) {
        XPRINT_ERR( 97, XERR_NULL_MSG );
        return XERR_NULL;
    }

    for ( i = 0; i < num_inputs; i++ ) {
        if ( lengths[i] < sizeof( header_block_t ) + sizeof( uint32_t ) ) {
            XPRINT_ERR( 1170, XERR_IO_MSG );
            return XERR_IO;
        }

    }

    lengths_tmp = 0;
    for ( i = 0; i < num_inputs; i++ ) {
        lengths_tmp = lengths_tmp + lengths[i] - ( sizeof( header_block_t ) + sizeof( uint32_t ) );
    }

    lengths_tmp = lengths_tmp + ( sizeof( header_block_t ) + sizeof( uint32_t ) );

    *o_length = lengths_tmp;

    return XSUCCESS;
}

int xrn_estimate_block_bytes_length_strip( uint64_t i_length, uint64_t * o_length
 )
{
    XPRINT_DEB(  );

    if ( o_length == NULL ) {
        XPRINT_ERR( 107, XERR_NULL_MSG );
        return XERR_NULL;
    }

    *o_length = i_length - sizeof( header_block_t ) - sizeof( uint32_t );

    return XSUCCESS;
}

int xrn_check_complete_blocks_bytes( uint8_t * i_bytes, uint64_t i_length
 )
{
    XPRINT_DEB(  );

    // Read the magic number from the input file
    header_block_t header;
    int i;
    uint8_t crc8, crc8_stored;

    if ( i_bytes == NULL ) {
        XPRINT_ERR( 138, XERR_NULL_MSG );
        return XERR_NULL;
    }

    for ( i = 0; i < sizeof( header ); i++ ) {
        ( ( uint8_t * ) & header )[i] = i_bytes[i];
    }

    if ( header.magic_number != XRN_MAGIC_NUMBER_BLOCK ) {
        XPRINT_ERR( 137, XERR_MAGIC_MSG );
        return XERR_MAGIC;
    }

    if ( header.offset < 0 || header.offset >= header.total_length ) {
        XPRINT_ERR( 92, XERR_IO_MSG );
        return XERR_IO;
    }

    if ( header.length == 0 || ( header.offset + header.length ) > header.total_length ) {
        XPRINT_ERR( 17, XERR_IO_MSG );
        return XERR_IO;
    }

    crc8_stored = header.crc8;
    header.crc8 = 0xff;
    crc8 = 0xff;
    xrn_crc8( ( uint8_t * ) & header, sizeof( header ) - 1, &crc8 );

    if ( crc8_stored != crc8 ) {
        XPRINT_ERR( 34, XERR_CRC_MSG );
        return XERR_CRC;
    }

    if ( ( header.offset == 0 ) && ( header.total_length == header.length ) ) {
        XPRINT_NOT( 0 );
        return XCOMPLETE;
    }

    XPRINT_NOT( 1 );
    return XNCOMPLETE;

}

int xrn_make_block_bytes( uint8_t * i_bytes, uint64_t i_length, uint8_t * o_bytes, uint64_t o_length,
                          uint32_t tag, uint64_t offset, uint64_t length
 )
{
    XPRINT_DEB(  );

    uint32_t crc;
    uint64_t oindex;
    uint64_t i;
    uint8_t crc8;

    header_block_t header;

    if ( ( i_bytes == NULL ) || ( o_bytes == NULL ) ) {
        XPRINT_ERR( 141, XERR_NULL_MSG );
        return XERR_NULL;
    }

    crc = 0xffffffff;

    for ( i = 0; i < sizeof( header ); i++ ) {
        ( ( uint8_t * ) & header )[i] = 0;
    }

    header.total_length = i_length;

    // Validate the offset and length
    if ( ( length < 0 ) || ( offset < 0 ) || ( offset + length > header.total_length ) ) {
        XPRINT_ERR( 140, XERR_IO_MSG );
        return XERR_IO;
    }

    if ( o_length != length + sizeof( header ) + sizeof( crc ) ) {
        XPRINT_ERR( 136, XERR_IO_MSG );
        return XERR_IO;
    }

    // Write the magic number to the output file

    header.magic_number = XRN_MAGIC_NUMBER_BLOCK;
    header.tag = tag;
    header.offset = offset;
    header.length = length;
    header.crc8 = 0xff;
    crc8 = 0xff;
    xrn_crc8( ( uint8_t * ) & header, sizeof( header ) - 1, &crc8 );
    header.crc8 = crc8;

    oindex = 0;
    for ( i = 0; i < sizeof( header ); i++ ) {
        o_bytes[oindex] = ( ( uint8_t * ) & header )[i];
        oindex++;
    }

    // Read and write the specified length of data from the input file to the output file
    for ( i = offset; i < offset + length; i++ ) {
        o_bytes[oindex] = i_bytes[i];
        xrn_crc32( &o_bytes[oindex], sizeof( o_bytes[oindex] ), &crc );
        oindex++;
    }

    for ( i = 0; i < sizeof( crc ); i++ ) {
        o_bytes[oindex] = ( ( uint8_t * ) & crc )[i];
        oindex++;
    }

    return XSUCCESS;
}

int xrn_strip_header_from_block_bytes( uint8_t * i_bytes, uint64_t i_length, uint8_t * o_bytes, uint64_t o_length
 )
{
    XPRINT_DEB(  );

    // Read the magic number from the input file
    uint32_t crc, crc_stored;
    uint8_t crc8, crc8_stored;
    uint64_t index, i;

    header_block_t header;

    if ( ( i_bytes == NULL ) || ( o_bytes == NULL ) ) {
        XPRINT_ERR( 134, XERR_NULL_MSG );
        return XERR_NULL;
    }

    if ( o_length > i_length - sizeof( header ) - sizeof( uint32_t ) ) {
        XPRINT_ERR( 125, XERR_IO_MSG );
        return XERR_IO;
    }

    crc = 0xffffffff;

    for ( i = 0; i < sizeof( header ); i++ ) {
        ( ( uint8_t * ) & header )[i] = i_bytes[i];
    }

    crc8_stored = header.crc8;
    header.crc8 = 0xff;
    crc8 = 0xff;
    xrn_crc8( ( uint8_t * ) & header, sizeof( header ) - 1, &crc8 );
    if ( crc8 != crc8_stored ) {
        XPRINT_ERR( 28, XERR_CRC_MSG );
        return XERR_CRC;
    }

    if ( header.magic_number != XRN_MAGIC_NUMBER_BLOCK ) {
        XPRINT_ERR( 132, XERR_MAGIC_MSG );
        return XERR_MAGIC;
    }

    if ( header.offset < 0 || header.offset >= header.total_length ) {
        XPRINT_ERR( 131, XERR_IO_MSG );
        return XERR_IO;
    }

    if ( header.length == 0 || ( header.offset + header.length ) > header.total_length ) {
        XPRINT_ERR( 130, XERR_IO_MSG );
        return XERR_IO;
    }

    index = sizeof( header );

    // Read and write the specified length of data from the input file to the output file
    for ( i = 0; i < header.length; i++ ) {

        o_bytes[i] = i_bytes[index];
        xrn_crc32( &o_bytes[i], sizeof( o_bytes[i] ), &crc );
        index++;

    }

    for ( i = 0; i < sizeof( crc_stored ); i++ ) {
        ( ( uint8_t * ) & crc_stored )[i] = i_bytes[index];
        index++;
    }

    if ( crc != crc_stored ) {
        XPRINT_ERR( 128, XERR_CRC_MSG );
        return XERR_CRC;
    }

    return XSUCCESS;
}

int xrn_join_blocks_bytes( uint8_t * i_bytes[XRN_MAX_MERGE_PARALLELISM],
                           uint64_t i_length[XRN_MAX_MERGE_PARALLELISM],
                           uint8_t number_of_inputs, uint8_t * o_bytes, uint64_t o_length
 )
{
    XPRINT_DEB(  );

    uint32_t crc_stored32[XRN_MAX_MERGE_PARALLELISM], crc32[XRN_MAX_MERGE_PARALLELISM], crc_output32;
    header_block_t headero, header[XRN_MAX_MERGE_PARALLELISM];
    uint64_t sorted_indexes[XRN_MAX_MERGE_PARALLELISM], sorted_offsets[XRN_MAX_MERGE_PARALLELISM], swap;
    uint64_t covered_length, i, j;
    uint64_t index[XRN_MAX_MERGE_PARALLELISM], stream_index, outindex;
    uint8_t c, first_char;
    uint8_t crc8[XRN_MAX_MERGE_PARALLELISM], crc8_stored[XRN_MAX_MERGE_PARALLELISM];

    if ( ( i_bytes == NULL ) || ( o_bytes == NULL ) ) {
        XPRINT_ERR( 780, XERR_NULL_MSG );
        return XERR_NULL;
    }

    if ( number_of_inputs < 2 ) {
        XPRINT_ERR( 771, XERR_IO_MSG );
        return XERR_IO;
    }

    for ( stream_index = 0; stream_index < number_of_inputs; stream_index++ ) {
        if ( i_bytes[stream_index] == NULL ) {
            XPRINT_ERR( 127, XERR_NULL_MSG );
            return XERR_NULL;
        }

        if ( i_length[stream_index] < sizeof( header_block_t ) ) {
            XPRINT_ERR( 94, XERR_IO_MSG );
            return XERR_IO;
        }

    }

    sorted_indexes[0] = 0;
    crc32[0] = 0xffffffff;
    for ( i = 1; i < XRN_MAX_MERGE_PARALLELISM; i++ ) {
        sorted_indexes[i] = i;
        crc32[i] = 0xffffffff;

    }

    // initialize outputs

    outindex = 0;
    crc_output32 = 0xffffffff;
    for ( i = 0; i < sizeof( header[0] ); i++ ) {
        ( ( uint8_t * ) & headero )[i] = 0;
    }

    // initialize inputs

    for ( stream_index = 0; stream_index < number_of_inputs; stream_index++ ) {

        index[stream_index] = 0;

        if ( i_length[stream_index] < sizeof( header[stream_index] ) ) {
            XPRINT_ERR( 29, XERR_IO_MSG );
            return XERR_IO;
        } else {
            for ( i = 0; i < sizeof( header[stream_index] ); i++ ) {
                ( ( uint8_t * ) & ( header[stream_index] ) )[i] = i_bytes[stream_index][index[stream_index]];

                index[stream_index]++;
            }

        }

        // check header

        crc8_stored[stream_index] = header[stream_index].crc8;
        header[stream_index].crc8 = 0xff;
        crc8[stream_index] = 0xff;
        xrn_crc8( ( uint8_t * ) & ( header[stream_index] ), sizeof( header[stream_index] ) - 1,
                  &( crc8[stream_index] ) );
        if ( crc8[stream_index] != crc8_stored[stream_index] ) {
            XPRINT_ERR( 95, XERR_CRC_MSG );
            return XERR_CRC;
        }

        // Validate the magic numbers

        if ( header[stream_index].magic_number != XRN_MAGIC_NUMBER_BLOCK ) {
            XPRINT_ERR( 123, XERR_MAGIC_MSG );
            return XERR_MAGIC;
        }

        if ( stream_index > 0 ) {

            // Validate total length
            if ( header[stream_index].total_length != header[stream_index - 1].total_length ) {
                XPRINT_ERR( 122, XERR_IO_MSG );
                return XERR_IO;
            }

            // Validate tag
            if ( header[stream_index].tag != header[stream_index - 1].tag ) {
                XPRINT_ERR( 121, XERR_IO_MSG );
                return XERR_IO;
            }

        }

    }

    // bouble sort by offset

    for ( i = 0; i < number_of_inputs; i++ ) {
        sorted_offsets[i] = header[i].offset;
        sorted_indexes[i] = i;
    }

    for ( i = 0; i < number_of_inputs - 1; i++ ) {
        for ( j = 0; j < number_of_inputs - 1 - ( int ) i; j++ ) {

            if ( sorted_offsets[j] > sorted_offsets[j + 1] ) {
                swap = sorted_offsets[j];
                sorted_offsets[j] = sorted_offsets[j + 1];
                sorted_offsets[j + 1] = swap;

                swap = sorted_indexes[j];
                sorted_indexes[j] = sorted_indexes[j + 1];
                sorted_indexes[j + 1] = swap;

            }

        }

    }

    // check if there are any empty spaces between contiguous blocks

    covered_length = header[sorted_indexes[0]].offset + header[sorted_indexes[0]].length;
    for ( stream_index = 1; stream_index < number_of_inputs; stream_index++ ) {

        // error empty space
        if ( header[sorted_indexes[stream_index]].offset > covered_length ) {
            XPRINT_ERR( 30, XERR_IO_MSG );
            return XERR_IO;
        } else {
            if ( covered_length <
                 header[sorted_indexes[stream_index]].offset + header[sorted_indexes[stream_index]].length ) {
                covered_length =
                    header[sorted_indexes[stream_index]].offset + header[sorted_indexes[stream_index]].length;
            }

        }

    }

    // initialize output header

    headero.magic_number = XRN_MAGIC_NUMBER_BLOCK;
    headero.total_length = header[0].total_length;
    headero.tag = header[0].tag;
    headero.offset = header[sorted_indexes[0]].offset;
    headero.length = covered_length - header[sorted_indexes[0]].offset;

    crc8[0] = 0xff;
    headero.crc8 = 0xff;
    xrn_crc8( ( uint8_t * ) & headero, sizeof( headero ) - 1, &crc8[0] );
    headero.crc8 = crc8[0];

    for ( i = 0; i < sizeof( headero ); i++ ) {
        o_bytes[outindex] = ( ( uint8_t * ) & headero )[i];
        outindex++;
    }

    // for each element in the output stream
    for ( i = headero.offset; i < headero.offset + headero.length; i++ ) {

        first_char = 0;
        for ( stream_index = 0; stream_index < number_of_inputs; stream_index++ ) {

            // if character is contained in the stream
            if ( ( i >= header[stream_index].offset )
                 && ( i < header[stream_index].length + header[stream_index].offset ) ) {

                // save the first character
                if ( first_char == 0 ) {
                    if ( index[stream_index] >= i_length[stream_index] ) {
                        XPRINT_ERR( 115, XERR_IO_MSG );
                        return XERR_IO;
                    } else {
                        c = i_bytes[stream_index][index[stream_index]];
                        xrn_crc32( &c, sizeof( c ), &crc32[stream_index] );
                        index[stream_index]++;
                    }

                    first_char = 1;

                    // check if the character is consistent for all the input streams
                } else {

                    if ( index[stream_index] >= i_length[stream_index] ) {
                        XPRINT_ERR( 120, XERR_IO_MSG );
                        return XERR_IO;
                    } else {
                        if ( c == i_bytes[stream_index][index[stream_index]] ) {
                            xrn_crc32( &c, sizeof( c ), &crc32[stream_index] );
                            index[stream_index]++;
                        } else {
                            XPRINT_ERR( 119, XERR_IO_MSG );
                            return XERR_IO;
                        }

                    }

                }

            }

        }

        xrn_crc32( &c, sizeof( c ), &crc_output32 );

        if ( outindex < o_length ) {
            o_bytes[outindex] = c;
            outindex++;
        } else {
            XPRINT_ERR( 124, XERR_IO_MSG );
            return XERR_IO;
        }

    }

    // check all the input crcs

    for ( stream_index = 0; stream_index < number_of_inputs; stream_index++ ) {

        for ( i = 0; i < sizeof( crc_stored32[stream_index] ); i++ ) {
            if ( index[stream_index] >= i_length[stream_index] ) {
                XPRINT_ERR( 774, XERR_IO_MSG );
                return XERR_IO;
            } else {
                ( ( uint8_t * ) & crc_stored32[stream_index] )[i] =
                    i_bytes[stream_index][index[stream_index]];
                index[stream_index]++;
            }

        }

        if ( crc32[stream_index] != crc_stored32[stream_index] ) {
            XPRINT_ERR( 96, XERR_CRC_MSG );
            return XERR_CRC;
        }

    }

    // write output crc

    for ( i = 0; i < sizeof( crc_output32 ); i++ ) {
        if ( outindex < o_length ) {
            o_bytes[outindex] = ( ( uint8_t * ) & crc_output32 )[i];
            outindex++;
        } else {
            XPRINT_ERR( 779, XERR_IO_MSG );
            return XERR_IO;
        }

    }

    return XSUCCESS;

}

